Skip to content

Conversation

tdelabro
Copy link
Collaborator

@tdelabro tdelabro commented Sep 16, 2025

Add a ContractAddress wrapper type that guarantee the felt it contains is a valid starknet contract address.
Contain some quality of like function for string conversion

Not a breaking change

Discussion:
@xJonathanLEI suggested we add ClassHash too
I chose to exclude addresses 0x0 and 0x1 from the normal ContractAddress flow. You can still build them with the unchecked one. Should we add constant ContractAddress::ZERO and ContractAddress::ONE to make those special case more explicit?
Do we want to allow them at all? Is there a risk to make some API unusable downstream if those value are entierly excluded?

@tdelabro tdelabro force-pushed the add-contract-address-type branch from bea90b3 to 02d6946 Compare September 16, 2025 15:15
Copy link
Collaborator

@gabrielbosio gabrielbosio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small comments

/// * 0x1 functions as a storage space for block mapping [link](https://docs.starknet.io/architecture-and-concepts/network-architecture/starknet-state/#special_addresses)
/// - They must be less than 2^251 (0x800000000000000000000000000000000000000000000000000000000000000)
///
/// Making the valid addressabe range be [2, 2^251)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Making the valid addressabe range be [2, 2^251)
/// Making the valid addressable range be [2, 2^251)

)
}
ContactAddressFromFeltError::TooBig => {
write!(f, "the highest possible address is 2^251 - 1")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also add the contract address value in the error message and replace 2^251 - 1 with the value of ADDRESS_UPPER_BOUND so we can more easily compare the failing contract address with the upper bound

@gabrielbosio gabrielbosio self-requested a review September 23, 2025 21:50
/// which would result in permanent loss.
impl Felt {
pub fn is_valid_contract_address(&self) -> bool {
self >= &Felt::from(2u64) && self < &ADDRESS_UPPER_BOUND
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self >= &Felt::from(2u64) && self < &ADDRESS_UPPER_BOUND
self >= &Felt::TWO && self < &ADDRESS_UPPER_BOUND

@tdelabro
Copy link
Collaborator Author

@gabrielbosio do you have an opinion about the points I highlighted in the issue description?

@gabrielbosio
Copy link
Collaborator

@gabrielbosio do you have an opinion about the points I highlighted in the issue description?

It depends if we want to migrate the ContractAddress struct from the sequencer repo. If that's the case then the try_from method should only check for the upper bound like it's done here.

@tdelabro
Copy link
Collaborator Author

@dorimedini-starkware you are maintaining the sequencer repo?
What do you think?
Is there a reason not to have a common ContractAddress shared between the whole ecosystem (sequencer, starknet-rs, ...)

@dorimedini-starkware
Copy link
Collaborator

@dorimedini-starkware you are maintaining the sequencer repo? What do you think? Is there a reason not to have a common ContractAddress shared between the whole ecosystem (sequencer, starknet-rs, ...)

no reason I can think of, but this suggested implementation differs from ours in several ways, like:

  1. we have another indirection (ContractAddress(pub PatriciaKey(Felt)) vs. ContractAddress(Felt))
  2. we allow zero and one as valid addresses
  3. we use derive_more::Display and derive_more::Deref
  4. serde derives are not feature-gated

@tdelabro
Copy link
Collaborator Author

@dorimedini-starkware

  1. If I remember correctly the intermediate indirection to PatriciaKey is made useless by the fact that the internal value is pub. Which mean you can freely overstep the types restriction ContractAddress(PatriciaKey(<Some totally non valid Felt value>)). So by making the internal hidden, and using From/TryFrom rather than value.0 we will increase the type safety, without need for internal nesting of types
  2. This make sense. I believe we can ma them valid values, and just add a checking method is_regular_contract_address(&self) -> bool that allow for users to check if they are not receiving special address values. An alternative is to have a second type, in the same way we have NonZeroFelt, RegularContractAddress (or something). Maybe the best solution, as it is still optional, but provide the best protection for users willing to use it
  3. Sure I can add those quality of life impl
  4. You can just enable the feature and it will be the same. But it is a good practice to have non-core features requiring the import of depencies (like serde , num-traits, and so on) feature gated. It doesn't cost much and is a good practice.

Comment on lines 57 to 62
ContactAddressFromFeltError::Zero => {
write!(
f,
"address 0x0 is reserved as the default caller address and has no storage"
)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think you want to allow address 0

@dorimedini-starkware
Copy link
Collaborator

@dorimedini-starkware

1. If I remember correctly the intermediate indirection to `PatriciaKey` is made useless by the fact that the internal value is `pub`. Which mean you can freely overstep the types restriction `ContractAddress(PatriciaKey(<Some totally non valid Felt value>))`. So by making the internal hidden, and using `From`/`TryFrom` rather than `value.0` we will increase the type safety, without need for internal nesting of types

2. This make sense. I believe we can ma them valid values, and just add a checking method `is_regular_contract_address(&self) -> bool` that allow for users to check if they are not receiving special address values. An alternative is to have a second type, in the same way we have `NonZeroFelt`, `RegularContractAddress` (or something). Maybe the best solution, as it is still optional, but provide the best protection for users willing to use it

3. Sure I can add those quality of life `impl`

4. You can just enable the feature and it will be the same. But it is a good practice to have non-core features requiring the import of depencies (like `serde` , `num-traits`, and so on) feature gated. It doesn't cost much and is a good practice.
  1. PatriciaKey is pub but it's internal Felt is not, and creating a PatriciaKey with an out of range Felt should panic. I'm not saying the safety changes, I'm saying changing this indirection will require wide refactors if and when we migrate our ContractAddress type
  2. .
  3. please do :)
  4. makes sense.

all in all I have no issue with this addition; it may take time for us to migrate to this new type though

@tdelabro
Copy link
Collaborator Author

@dorimedini-starkware so do you want this crate to also add a PatriciaKey type. Would it be helpful for you?

@dorimedini-starkware
Copy link
Collaborator

@dorimedini-starkware so do you want this crate to also add a PatriciaKey type. Would it be helpful for you?

I guess it would make the transition less painful, so sure.
We use PatriciaKey to describe an index in any of our state trees (only one of which is actually indexed by contract addresses) - the class tree and each storage tree also need to be bound by the patricia key limit

@tdelabro
Copy link
Collaborator Author

tdelabro commented Sep 30, 2025

@dorimedini-starkware going back to point 2.

using derive_more::Display, or manual impl end up being the same. So no worries here

Now regarding derive_more::Deref, we won't implement it and will ask you to use as_ref or into instead.
Indeed the Deref trait purpose is to create smart-pointers, to add on the native ownership semantic, not to perform implicit type conversion. I understand the attraction of having the compiler quietly do those for you, but this is not what this trait is designed for and can be surprising for the user.
It is a bit more verbose, but for the best.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants